package com.ityuwen.mengyagushi.album.util;

import org.jetbrains.annotations.NotNull;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

public class DistributedLock implements Lock {

    private Long startTime; // 获取锁的起始时间
    private String lockName;
    private String uuid;
    private Integer expire = 30;

    private RedisTemplate redisTemplate;

    public DistributedLock(RedisTemplate redisTemplate, String lockName, String uuid, Integer expire) {
        this.startTime = System.currentTimeMillis();
        this.redisTemplate = redisTemplate;
        this.lockName = lockName;
        this.expire = expire;
        this.uuid = uuid;
    }

    public DistributedLock(RedisTemplate redisTemplate, String lockName, String uuid) {
        this.startTime = System.currentTimeMillis();
        this.redisTemplate = redisTemplate;
        this.lockName = lockName;
        this.uuid = uuid;
    }

    @Override
    public void lock() {
        this.tryLock();
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public boolean tryLock() {
        try {
            return this.tryLock(-1, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public boolean tryLock(long time, @NotNull TimeUnit unit) throws InterruptedException {

        // 判断是否超时
        if (time != -1 && time > 0){
            long now = System.currentTimeMillis();
            if (now - startTime > unit.toMillis(time)) {
                throw new RuntimeException("获取锁超时！");
            }
        }

        // 获取锁
        String script = "if redis.call('exists', KEYS[1]) == 0 or redis.call('hexists', KEYS[1], ARGV[1]) == 1 " +
                "then " +
                "   redis.call('hincrby', KEYS[1], ARGV[1], 1) " +
                "   redis.call('expire', KEYS[1], ARGV[2]) " +
                "   return 1 " +
                "else " +
                "   return 0 " +
                "end";
        Boolean flag = (Boolean)this.redisTemplate.execute(new DefaultRedisScript(script, Boolean.class), Arrays.asList(lockName), uuid, expire);
        if (!flag){
            // 如果获取锁失败，则进行重试
            Thread.sleep(40);
            this.tryLock(time, unit);
        }

        // 加锁成功之后，返回之前开启进行自动续期的定时任务
        this.renewExpiration();
        return true;
    }

    private void renewExpiration(){
        String script = "if redis.call('hexists', KEYS[1], ARGV[1]) == 1 " +
                "then " +
                "   return redis.call('expire', KEYS[1], ARGV[2]) " +
                "else " +
                "   return 0 " +
                "end";
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                Boolean flag = (Boolean)redisTemplate.execute(new DefaultRedisScript(script, Boolean.class), Arrays.asList(lockName), uuid, expire);
                if (flag){
                    // 如果上一次续期成功，可能就需要下一次的自动续期
                    renewExpiration();
                }
            }
        }, expire * 1000 / 3);
    }

    @Override
    public void unlock() {
        String script = "if redis.call('hexists', KEYS[1], ARGV[1]) == 0 " +
                "then " +
                "   return nil " +
                "elseif redis.call('hincrby', KEYS[1], ARGV[1], -1) == 0 " +
                "then " +
                "   return redis.call('del', KEYS[1]) " +
                "else " +
                "   return 0 " +
                "end";
        // 此处的返回类型不能是bool，因为nil对应也是false。使用Long：null 0 1
        Long flag = (Long)this.redisTemplate.execute(new DefaultRedisScript(script, Long.class), Arrays.asList(lockName), uuid);
        if (flag == null){
            throw new IllegalMonitorStateException("你要释放的锁不属于你！");
        }
    }

    @NotNull
    @Override
    public Condition newCondition() {
        return null;
    }
}
